/*
* All code is only for jiecai 
* All rights reserved
*/
package com.jc.common;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Timer;
import java.util.TimerTask;

import org.apache.log4j.Logger;

import com.jc.common.util.RequestUtil;

/**
 * @author kenny 
 * created on 2014-10-15
 */
public class StaticConfig {

	private static int isInitialized = 0;
	/**
	 * Logger for this class
	 */
	private static final Logger logger = Logger.getLogger(StaticConfig.class);

	private static final String VERSION_FILE = "version.txt";
	
	private static Properties configInfo = null;

	// 静态文件路径前缀
	private static String prefix_css = null;
	private static String prefix_js = null;
	private static String prefix_image = null;
	private static String prefix_swf = null;
	private static String prefix_static = null;
	private static String prefix_www = null;
	
	private static String cachedVersionPath;
	private static String versionService;
	private static long checkInterval = 0L;
	
	private static Map<String, String> versionMap;
	
	private static Timer timer;
	private static boolean isInstalled = false;
	/**
	 * 安装静态资源版本组件。任何需要使用静态资源版本的服务，都需要调用该方法。
	 */
	public static synchronized void install(){
		if (isInstalled) {
			if (logger.isInfoEnabled()) {
				logger.info("The static component has been installed!");
			}
			return;
		}
		init();//初始化参数
		loadCachedVersion(); //从本地读取缓存
		timer = new Timer("update_version_timer");
		timer.schedule(new TimerTask() {//定时检查版本信息

			@Override
			public void run() {		
				try {
					toUpdate();
				} catch (Throwable t) {
					logger.error("Updating version failed: " + t.getMessage(), t);
				}
			}
			
		}, checkInterval, checkInterval);
		
		isInstalled = true;
	}
	
	public static synchronized void uninstall() {
		if (versionMap != null) {
			versionMap.clear();
		}
		
		if (timer != null) {
			timer.cancel();
		}
	}
	
	public static boolean isInstalled() {
		return isInstalled;
	}
	
	private static void init() {
		InputStream in = null;
		try {
			in = StaticConfig.class.getClassLoader()
					.getResourceAsStream("StaticConfig.properties");
			if (in == null) {
				throw new RuntimeException(
						"The file: StaticConfig.properties can't be found in Classpath.");
			}

			Properties prop = new Properties();
			prop.load(in);

			configInfo = prop;

			prefix_css = prop.getProperty("prefix_css", "//def");
			prefix_js = prop.getProperty("prefix_js", "//def");
			prefix_image = prop.getProperty("prefix_image", "//def");
			prefix_swf = prop.getProperty("prefix_swf", "//def");
			prefix_static = prop.getProperty("prefix_static", "//def");
			prefix_www = prop.getProperty("prefix_www", "//def");
			
			cachedVersionPath = prop.getProperty("versionpath");
			versionService = prop.getProperty("version_service", "//def");
			checkInterval = Long.parseLong(prop.getProperty("update_interval", "30000"));
			isInitialized = 1;
		} catch (IOException e) {
			logger.error("Error occurs: " + e.getMessage(), e);
		} finally {
			if (in != null) {
				try {
					in.close();
				} catch (IOException e) {
				}
			}
		}
	}

	/**
	 * 首次从本地加载版本缓存信息。如果本地没有缓存，则从服务端获取
	 */
	private static void loadCachedVersion() {
		InputStream in = null;
		try {
			File versionFile = new File(cachedVersionPath, VERSION_FILE);
			if (logger.isInfoEnabled()) {
				logger.info("Version File: " + versionFile.getAbsolutePath());
			}
			if (!versionFile.getParentFile().exists()) {
				versionFile.getParentFile().mkdirs();
			}
			
			if (!versionFile.exists()) {//缓存文件不存在，从服务端获取,同时保存至本地
				if (logger.isInfoEnabled()) {
					logger.info("Load version information from version service in initalizing");
				}
				ByteArrayOutputStream rawBytes;
				try {
					rawBytes = getRawVersionData();
				} catch (IOException e) {
					throw new RuntimeException("Retrieving from version service failed: " + e.getMessage(), e);
				}
				in = new ByteArrayInputStream(rawBytes.toByteArray(), 0, rawBytes.size());
				
				FileOutputStream outputStream = null;
				try {
					outputStream = new FileOutputStream(versionFile);
					outputStream.write(rawBytes.toByteArray(), 0, rawBytes.size());
					outputStream.flush();
				} catch (IOException e) {
					logger.error(e);
				} finally {
					if (outputStream != null) {
						try {
							outputStream.close();
						} catch (IOException e) {
						}
					}
					try {
						if(rawBytes!= null)
							rawBytes.close();
					} catch (IOException e) {
					}
				}
				
				
			} else { //文件存在，则直接从本地读取版本信息
				try {
					in = new FileInputStream(versionFile);
				} catch (FileNotFoundException e) {
					logger.error(e);
				}
			}
			
			Map<String, String> versionMap = readByInputStream(in);//转化为HashMap以提升访问性能
			
			if (StaticConfig.versionMap != null) {
				StaticConfig.versionMap.clear();
			}
			StaticConfig.versionMap = versionMap;
		} finally {
			if (in != null) {
				try {
					in.close();
				} catch (IOException e) {
				}
			}
		}
	}
	
	private static Map<String, String> readByInputStream(InputStream in) {
		Properties prop = new Properties();
		try {
			prop.load(in);
		} catch (IOException e) {
			logger.error("The format of version information is incorrect!", e);
			throw new RuntimeException("The format of version information is incorrect!", e);
		}
		
		Map<String, String> versionMap = new HashMap<String, String>();//转化为HashMap以提升访问性能
		for (Map.Entry<Object, Object> entry : prop.entrySet()) {
			versionMap.put((String)entry.getKey(), (String)entry.getValue());
		}
		prop.clear();
		
		return versionMap;
	}
	/**
	 * 执行更新动作。
	 * 先检查是否有变化，如果有变化，则获取最新的版本信息。
	 */
	public static void toUpdate() {
		if (checkVersionChanged()) {
			if (logger.isInfoEnabled()) {
				logger.info("The version content is changed.");
			}
			updateVersion();
		}
	}
	/**
	 * 检查版本是否有更新
	 * @return
	 */
	public static boolean checkVersionChanged() {
		if (versionMap == null) {
			return true;
		}
		String lastVersion = versionMap.get("version");
		if (lastVersion == null || lastVersion.equals("")) {
			return true;
		}
		
		String lastestVersion = RequestUtil.getRequest(versionService + "/c?action=v_version", Constant.UTF8);
		int numV = -1;
		try {
			numV = Integer.parseInt(lastestVersion);
		} catch (Exception e) {
			return false;//版本服务端出问题，不更新，等待下次检查
		}
		
		int lastNumV = -1;
		try {
			lastNumV = Integer.parseInt(lastVersion);
		} catch (Exception e) {
			return true;//本地版本问题，需要重新从版本服务器下载数据
		}
		if (logger.isDebugEnabled()) {
			logger.debug("service version:" + numV + ", current version:" + lastNumV);
		}
		
		return numV > lastNumV;
	}
	/**
	 * 1. 从版本管理服务中获取最新的版本信息
	 * 2. 加载至内存
	 * 3. 保存至本地缓存
	 */
	public static void updateVersion() {
		if (logger.isInfoEnabled()) {
			logger.info("Load version information from version service.");
		}
		InputStream in = null;
		ByteArrayOutputStream rawBytes = null;
		FileOutputStream outputStream = null;
		try {
			//1.
			try {
				rawBytes = getRawVersionData();
			} catch (IOException e) {
				throw new RuntimeException("Retrieving from version service failed: " + e.getMessage(), e);
			}
			
			//2
			in = new ByteArrayInputStream(rawBytes.toByteArray(), 0, rawBytes.size());
				Map<String, String> versionMap = readByInputStream(in);//转化为HashMap以提升访问性能
			
				if (StaticConfig.versionMap != null) {
					StaticConfig.versionMap.clear();
				}
				StaticConfig.versionMap = versionMap;
	
			//3
			try {
				File versionFile = new File(cachedVersionPath, VERSION_FILE);
				if (!versionFile.getParentFile().exists()) {
					versionFile.getParentFile().mkdirs();
				}
				outputStream = new FileOutputStream(versionFile);
				outputStream.write(rawBytes.toByteArray(), 0, rawBytes.size());
				outputStream.flush();
				outputStream.close();
			} catch (IOException e) {
				logger.error(e);
				if (outputStream != null) {
					try {
						outputStream.close();
					} catch (IOException ex) {
					}
				}
			}
			
			
		} finally {
			if (in != null) {
				try {
					in.close();
				} catch (IOException e) {
				}
			}
			if (rawBytes != null) {
				try {
					rawBytes.close();
				} catch (IOException e) {
				}
			}
			

		}
	}
	public static String js(String key) {
		if (key == null || "".equals(key)) {
			return "";
		}
		
		String versionKey = versionMap.get(key);
		if (versionKey == null) {
			versionKey = key;
		}
		return prefix_js + versionKey;
	}
	public static String css(String key) {
		if (key == null || "".equals(key)) {
			return "";
		}
		
		String versionKey = versionMap.get(key);
		if (versionKey == null) {
			versionKey = key;
		}
		return prefix_css + versionKey;
	}
	public static String img(String key) {
		if (key == null || "".equals(key)) {
			return "";
		}
		
		String versionKey = versionMap.get(key);
		if (versionKey == null) {
			versionKey = key;
		}
		return prefix_image + versionKey;
	}
	public static String www(String key) {
		if (key == null || "".equals(key)) {
			return "";
		}
		
		String versionKey = versionMap.get(key);
		if (versionKey == null) {
			versionKey = key;
		}
		return prefix_www + versionKey;
	}
	public static String swf(String key) {
		if (key == null || "".equals(key)) {
			return "";
		}
		
		String versionKey = versionMap.get(key);
		if (versionKey == null) {
			versionKey = key;
		}
		return prefix_swf + versionKey;
	}
	public static String res(String key) {
		if (key == null || "".equals(key)) {
			return "";
		}
		
		String versionKey = versionMap.get(key);
		if (versionKey == null) {
			versionKey = key;
		}
		return prefix_static + versionKey;
	}
	//
	/**
	 * 转换静态文件的url为实际部署的路径.<br>
	 * '/js/scene/a.js' --> 'http://ddd.ccc.c/js/scene.js' <br>
	 * 'js/ss.js' --> 'js/ss.js'
	 * 
	 * @param key
	 * @return
	 */
	public static String u(String key) {
		if (key == null || "".equals(key)) {
			return key;
		}
		key = key.replace("\\", "/");
		int startIndex = key.indexOf("/");
		int endIndex = key.indexOf("/", startIndex + 1);
		if (startIndex != -1 && endIndex != -1
				&& startIndex != key.length() - 1 && startIndex < endIndex) {
			String suffix = key.substring(startIndex + 1, endIndex);
			if (suffix.equalsIgnoreCase("css")) {
				return prefix_css + key;
			} else if (suffix.equalsIgnoreCase("js")) {
				return prefix_js + key;
			} else if (suffix.equalsIgnoreCase("swf")) {
				return prefix_swf + key;
			} else if (suffix.equalsIgnoreCase("i")) {
				return prefix_image + key;
			} else {
				int dotIndex = key.lastIndexOf(".");
				if (dotIndex != -1 && dotIndex + 1 < key.length()) {
					suffix = key.substring(dotIndex + 1);
				} else {
					return prefix_static + key;
				}
				
				if (suffix.equalsIgnoreCase("css")) {
					return prefix_css + key;
				} else if (suffix.equalsIgnoreCase("js")) {
					return prefix_js + key;
				} else if (suffix.equalsIgnoreCase("swf")) {
					return prefix_swf + key;
				} else if (suffix.equalsIgnoreCase("i")) {
					return prefix_image + key;
				}
				
				return prefix_static + key;
			}
		} else {
			// modified by morgan
			return configInfo.getProperty(key, key);
		}
	}

	public static String getPrefixCss() {
		return prefix_css;
	}

	public static String getPrefixJs() {
		return prefix_js;
	}

	public static String getPrefixSwf() {
		return prefix_swf;
	}

	public static String getPrefixStatic() {
		return prefix_static;
	}

	public static String getPrefixImage() {
		return prefix_image;
	}
	public static String getWww() {
		return prefix_www;
	}
	
	private static ByteArrayOutputStream getRawVersionData() throws IOException {
		HttpURLConnection httpURLConnection = null;
		InputStream inputStream = null;
		try {
			URL url = new URL(versionService + "/data");
			httpURLConnection = (HttpURLConnection) url
					.openConnection();
			httpURLConnection.setDoOutput(true);
			httpURLConnection.setRequestProperty("User-agent","51jiecai.com");
			httpURLConnection.setRequestMethod("GET");
			httpURLConnection.setReadTimeout(20000);
			httpURLConnection.connect();
	
			inputStream = (InputStream) httpURLConnection
					.getInputStream();
			
			ByteArrayOutputStream bops = new ByteArrayOutputStream();
			int count = 0;
			byte[] b = new byte[1024];
			while ((count = inputStream.read(b)) != -1) {
				bops.write(b, 0, count);
			}
			
			return bops;
		} finally {
			if (inputStream != null) {
				inputStream.close();
			}
			if (httpURLConnection != null) {
				httpURLConnection.disconnect();
			}
		}
	}

	public StaticConfig() {
		if (isInitialized == 0) {
			init();
		}
	}
	
}
